package com.sl.sdn.repository.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.sl.sdn.dto.OrganDTO;
import com.sl.sdn.dto.TransportLineNodeDTO;
import com.sl.sdn.entity.node.AgencyEntity;
import com.sl.sdn.enums.OrganTypeEnum;
import com.sl.sdn.repository.TransportLineRepository;
import org.neo4j.driver.internal.value.PathValue;
import org.neo4j.driver.types.Path;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Component
public class TransportLineRepositoryImpl implements TransportLineRepository {
    @Resource
    private Neo4jClient neo4jClient;
    /**
     * 查找从起点网点到终点网点之间的最短路径
     *
     * @param start 起始网点实体
     * @param end   终点网点实体
     * @return 包含路径信息的 DTO 对象（节点列表 + 总成本）
     */
    @Override
    public TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end) {
        // 获取当前类对应的 Neo4j 节点标签类型（即图数据库中节点的标签名称）
        String type=AgencyEntity.class.getAnnotation(Node.class).value()[0];

        // 构建 Cypher 查询语句：
        // - shortestPath(...) 表示查找最短路径
        // - [*..10] 表示路径最大深度不超过 10 层
        // - WHERE 子句用于根据 bid 精确匹配起始和结束节点
        String cypherQuery = StrUtil.format("MATCH path = shortestPath((start:{}) -[*..10]-> (end:{}))\n" +
                "WHERE start.bid = $startId AND end.bid = $endId \n" +
                "RETURN path", type, type);

        // 执行查询并映射结果
        Optional<TransportLineNodeDTO> optional=this.neo4jClient.query(cypherQuery)
                // 绑定参数：将 start.getBid() 设置为 $startId
                .bind(start.getBid()).to("startId")
                // 绑定参数：将 end.getBid() 设置为 $endId
                .bind(end.getBid()).to("endId")
                // 指定返回的数据类型为 TransportLineNodeDTO（实际是通过 mappedBy 自定义映射）
                .fetchAs(TransportLineNodeDTO.class)
                // 使用 mappedBy 方法对返回的记录进行自定义封装
                .mappedBy((typeSystem, record) -> {
                    // 获取第一条记录的第一个字段，即 Path 类型的路径数据
                    PathValue pathValue = (PathValue) record.get(0);
                    Path path = pathValue.asPath();

                    // 创建最终要返回的 DTO 对象
                    TransportLineNodeDTO dto = new TransportLineNodeDTO();

                    // 提取路径中的所有节点，并转换为 OrganDTO 列表
                    List<OrganDTO> nodeList = StreamUtil.of(path.nodes())
                            .map(node -> {
                                // 获取节点的所有属性 map 数据
                                Map<String, Object> map = node.asMap();
                                // 将 map 转换为 OrganDTO 对象（忽略异常字段）
                                OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class);

                                // 获取该节点的所有标签（labels），取第一个作为类型
                                OrganTypeEnum organTypeEnum = OrganTypeEnum.valueOf(CollUtil.getFirst(node.labels()));

                                // 设置组织类型码值
                                organDTO.setType(organTypeEnum.getCode());

                                // 地理位置信息：Neo4j 中 location 是 Point 类型，x 表示经度，y 表示纬度
                                organDTO.setLatitude(BeanUtil.getProperty(map.get("location"), "y"));
                                organDTO.setLongitude(BeanUtil.getProperty(map.get("location"), "x"));
                                return organDTO;
                            })
                            .collect(Collectors.toList());
                    // 设置路径上的所有节点
                    dto.setNodeList(nodeList);

                    // 提取路径上所有关系（Relationship）中的 cost 字段，并求和计算总成本
                    double cost = StreamUtil.of(path.relationships())
                            .mapToDouble(relationship -> {
                                Map<String, Object> objectMap = relationship.asMap();
                                return Convert.toDouble(objectMap.get("cost"), 0d);
                            }).sum();
                    dto.setCost(cost);

                    // 对总成本保留两位小数
                    dto.setCost(NumberUtil.round(dto.getCost(), 2).doubleValue());
                    return dto;
                }).one();

        // 如果有结果则返回，否则返回 null
        return optional.orElse(null);
    }
}
